package clickstreamhack;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

import com.google.common.io.ByteStreams;
import com.opensymphony.clickstream.Clickstream;
import com.opensymphony.clickstream.ClickstreamFilter;

/**
 * Put this filter before {@link ClickstreamFilter}} and use cracked viewstream.jsp 
 * then you can see request's headers and body
 *
 */
public class Clickstream102HackFilter implements Filter {

    /**
     * The click stream (individual) attribute key: this is
     * the one inserted into the HttpSession.
     */
    public static final String SESSION_ATTRIBUTE_KEY = "clickstream";
    
	public static final String REQUEST_BODY = Clickstream102HackFilter.class + ".requestbody";

	private static final String REQUEST_HEADERS = Clickstream102HackFilter.class + ".requestHeaders";
	
	private static ThreadLocal<HttpServletRequest> requestContext = new ThreadLocal<HttpServletRequest>();
	
	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
		throws IOException, ServletException {

		chain.doFilter(inspect((HttpServletRequest) request), response);
	}

	private HttpServletRequest inspect(HttpServletRequest request) throws IOException {
		final byte[] content = ByteStreams.toByteArray(request.getInputStream());
		hack(request, content);
		return wrappedRequest(request, content);
	}

	private void hack(HttpServletRequest request, final byte[] content) {
		request.setAttribute(REQUEST_BODY, new String(content));
		request.setAttribute(REQUEST_HEADERS, headers(request));
		
		requestContext.set(request);
        HttpSession session = request.getSession();
        Clickstream stream = (Clickstream) session.getAttribute(SESSION_ATTRIBUTE_KEY);
        if (needHack(stream)) {
            session.setAttribute(SESSION_ATTRIBUTE_KEY, hackedStream(stream, content));        	
        }
	}
	
	private Map<String, Enumeration> headers(HttpServletRequest request) {
		Map<String, Enumeration> headers = new HashMap<String, Enumeration>();
		
		Enumeration<String> headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String name = headerNames.nextElement();
			headers.put(name, request.getHeaders(name));
		}
		
		return headers;
	}

	public static String getReqeustBody() {
		return (String) getRequest().getAttribute(REQUEST_BODY);
	}

	public static Map<String, Enumeration> getReqeustHeaders() {
		return (Map<String, Enumeration>) getRequest().getAttribute(REQUEST_HEADERS);
	}
	
	public static HttpServletRequest getRequest() {
		return requestContext.get();
	}

	private boolean needHack(Clickstream stream) {
		return !HackedClickstream.class.isInstance(stream);
	}

	private HackedClickstream hackedStream(Clickstream stream, byte[] content) {
		return new HackedClickstream(stream);
	}

	private HttpServletRequest wrappedRequest(ServletRequest request, final byte[] content) {
		return new HttpServletRequestWrapper((HttpServletRequest) request) {
			public ServletInputStream getInputStream() throws IOException {
				return new ServletInputStream() {
					int index = 0;
					public int read() throws IOException {
						if (index >= content.length) {
							return -1;
						}
						return content[index++];
					}
				};
			}
		};
	}

	public void init(FilterConfig filterConfig) throws ServletException {
	}

}
